package tools.clustering.isodata;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import tools.utils.DoubleMatrix;

public class isodata_datum implements Serializable{

	private HashMap<Integer, isodata_data> datum;
	private HashMap<Integer, Integer> distancesMap,distancesMap_reverse;
	private ArrayList<Integer> allIndices;
	private DoubleMatrix distances;
	
	/**
	 * for datum
	 * @param inFile
	 * @throws Exception
	 */
	public isodata_datum(String inFile)throws Exception{
		datum=new HashMap<Integer, isodata_data>();
		BufferedReader in=new BufferedReader(new FileReader(inFile));
		String[] l;
		DoubleMatrix tmp;
		int j=0;
		Integer zero=new Integer(0);
		allIndices=new ArrayList<Integer>();
		for(String s=in.readLine();s!=null;s=in.readLine(),j++){
			if (s.length()>0) {
				l=s.split("\t");
				tmp=new DoubleMatrix(1,l.length);
				for (int i = 0; i < l.length; i++) {
					tmp.set(0,i,Double.parseDouble(l[i]));
				}
				datum.put(new Integer(j),new isodata_data(tmp,zero,new Integer(j)));
				allIndices.add(new Integer(j));
			}
		}
	}
	public isodata_datum(String inFile,String nameIndexFile,String subsetFile)throws Exception{
		datum=new HashMap<Integer, isodata_data>();
		ArrayList<String> subset=new ArrayList<String>();
		ArrayList<Integer> indices=new ArrayList<Integer>();
		String[] l;
		BufferedReader in =new BufferedReader(new FileReader(subsetFile));
		for(String s=in.readLine();s!=null;s=in.readLine()){
			if(s.length()>0){
				subset.add(s);
			}
		}
		in=new BufferedReader(new FileReader(nameIndexFile));
		for(String s=in.readLine();s!=null;s=in.readLine()){
			if(s.length()>0){
				l=s.split("\t");
				if(subset.contains(l[1])){
					indices.add(new Integer(l[0]));
				}
			}
		}
		in=new BufferedReader(new FileReader(inFile));
		DoubleMatrix tmp;
		int j=0;
		Integer zero=new Integer(0);
		allIndices=new ArrayList<Integer>();
		for(String s=in.readLine();s!=null;s=in.readLine(),j++){
			if(indices.contains(j)){
				if (s.length()>0) {
					l=s.split("\t");
					tmp=new DoubleMatrix(1,l.length);
					for (int i = 0; i < l.length; i++) {
						tmp.set(0,i,Double.parseDouble(l[i]));
					}
					datum.put(new Integer(j),new isodata_data(tmp,zero,new Integer(j)));
					allIndices.add(new Integer(j));
				}
			}
		}
	}
	public isodata_datum(String inFile,String nameIndexFile,String subsetFile,boolean reduced)throws Exception{
		if(!reduced){
			throw new Exception("Use isodata_datum(String inFile,String nameIndexFile,String subsetFile) instead");
		}
		datum=new HashMap<Integer, isodata_data>();
		ArrayList<String> subset=new ArrayList<String>();
		ArrayList<Integer> indices=new ArrayList<Integer>();
		String[] l;
		BufferedReader in =new BufferedReader(new FileReader(subsetFile));
		for(String s=in.readLine();s!=null;s=in.readLine()){
			if(s.length()>0){
				subset.add(s);
			}
		}
		in=new BufferedReader(new FileReader(nameIndexFile));
		for(String s=in.readLine();s!=null;s=in.readLine()){
			if(s.length()>0){
				l=s.split("\t");
				if(subset.contains(l[1])){
					indices.add(new Integer(l[0]));
				}
			}
		}
		Collections.sort(indices);
		in=new BufferedReader(new FileReader(inFile));
		DoubleMatrix tmp;
		int j=0;
		Integer zero=new Integer(0);
		allIndices=new ArrayList<Integer>();
		for(String s=in.readLine();s!=null;s=in.readLine(),j++){
			if(indices.contains(j)){
				if (s.length()>0) {
					l=s.split("\t");
					tmp=new DoubleMatrix(1,indices.size());
					for (int i = 0; i < indices.size(); i++) {
						tmp.set(0,i,Double.parseDouble(l[indices.get(i).intValue()]));
					}
					datum.put(new Integer(j),new isodata_data(tmp,zero,new Integer(j)));
					allIndices.add(new Integer(j));
				}
			}
		}
	}
	/**
	 * for centroids
	 * @param in
	 * @throws Exception
	 */
	public isodata_datum(ArrayList in)throws Exception{
		datum=new HashMap<Integer, isodata_data>();
		isodata_data tmp;
		for (Iterator iter = in.iterator(); iter.hasNext();) {
			tmp=(isodata_data)iter.next();
			datum.put(tmp.getCluster(),tmp);
		}
		this.calculateDistances(true);
	}
	
	public void putData(Integer i,isodata_data d){
		datum.put(i,d);
	}
	public void remove(Integer i){
		datum.remove(i);
	}
	
	public double getDistanceToCentroid(Integer i){
		return ((isodata_data)datum.get(i)).getDistanceToCentroid();
	}
	public void setDistanceToCentroid(Integer i,double d){
		((isodata_data)datum.get(i)).setDistanceToCentroid(d);
	}
	
	public Collection<isodata_data> getDatum(){
		return datum.values();
	}
	public Set<Integer> keySet(){
		return datum.keySet();
	}
	public boolean hasKey(Integer i){
		return datum.containsKey(i);
	}
	public isodata_data get(Integer i){
		return datum.get(i);
	}
	public Object getData(Integer i){
		return datum.get(i).getData();
	}
	public void setData(Integer i,DoubleMatrix b){
		((isodata_data)datum.get(i)).setData(b);
	}
	public void setAllCentroids(isodata_datum centroid){
		for (isodata_data i : datum.values()) {
			i.setCentroids(centroid);
		}
	}
	public void setAllDataDistances(DoubleMatrix d){
		for (Iterator iter = datum.values().iterator(); iter.hasNext();) {
			isodata_data i = (isodata_data) iter.next();
			i.setDistances(d);
		}
	}
	public void setAllDataDistancesAndOrders(isodata_datum centroids)throws Exception{
		HashMap<Integer, Integer> numberToPosition=new HashMap<Integer, Integer>();
		ArrayList<DoubleMatrix> dist=new ArrayList<DoubleMatrix>();
		ArrayList<ArrayList<Integer>> order=new ArrayList<ArrayList<Integer>>();
		DoubleMatrix tmp;
		Object[] keyset=centroids.keySet().toArray();
//		for (int i = 0; i < keyset.length; i++) {
//			System.out.println(((Integer)keyset[i]).toString());
//		}
		for(int i=0;i<keyset.length;i++){
			tmp=centroids.getDistancesRow((Integer)keyset[i]);
			dist.add(tmp);
			ArrayList<Integer> t2=new ArrayList<Integer>();
			for (Integer t0 : tmp.sort()) {
				t2.add(centroids.distancesMap_reverse.get(t0));
			}
			order.add(t2);
			numberToPosition.put((Integer)keyset[i],new Integer(i));
		}
		for (Iterator iter = datum.values().iterator(); iter.hasNext();) {
			isodata_data i = (isodata_data) iter.next();
			if (!(i.getCluster().intValue()==0)) {
			i.setDistances((DoubleMatrix)dist.get(((Integer)numberToPosition.get(i.getCluster())).intValue()));
//			boolean exists=false;
//			for (int j = 0; j < keyset.length; j++) {
//				if(i.getCluster().intValue()==((Integer)keyset[j]).intValue()){
//					exists=true;
//				}
//			}
//			if(!exists){
//				System.out.println("Non-Existent: "+i.getCluster());
//			}
			i.setOrder((ArrayList<Integer>)order.get((numberToPosition.get(i.getCluster())).intValue()));
			}
		}
	}
	public DoubleMatrix getDistancesRow(Integer i)throws Exception{
		return distances.getRow(distancesMap.get(i).intValue());
	}
	
	public ArrayList<Integer> getAllIndices() {
		return allIndices;
	}
	public Integer getCluster(Integer i){
		return ((isodata_data)datum.get(i)).getCluster();
	}
	public void setCluster(Integer i,Integer cluster){
		((isodata_data)datum.get(i)).setCluster(cluster);
	}
	public int size(){
		return datum.size();
	}
	public double getDistance(Integer i,Integer j)throws Exception{
		if(i.intValue()==0||j.intValue()==0){
			return 999999;
		}else{
			return distances.get(distancesMap.get(i).intValue(),distancesMap.get(j).intValue());
		}
	}
	public double getDistance(int i)throws Exception{
		return distances.get(i);
	}
	public ArrayList<Integer> getDistanceOrder() throws Exception{
		DoubleMatrix tmp=new DoubleMatrix(distances);
		for(int i=0;i<tmp.getHeight();i++){
			for(int j=0;j<=i;j++){
				tmp.set(i, j, 99999999);
			}
		}
		return tmp.sort();
	}
	public List<int[]> getNClosestClusters(int N,double max)throws Exception{
		ArrayList<int[]> pair=new ArrayList<int[]>();
		ArrayList<Double> tmp=new ArrayList<Double>();
		double tDouble;
		N=N>datum.size()?datum.size():N;
		int n;
		for (int i = 0; i < distances.getHeight(); i++) {
			for (int j = 0; j < i; j++) {
				tDouble=distances.get(i, j);
				if(tDouble<max){
					n=0;
					for(;n<N&&n<tmp.size();n++){
						if(tDouble<tmp.get(n).doubleValue()){
							pair.add(n,new int[]{i,j});
							tmp.add(n, new Double(tDouble));
							break;
						}
					}
					if(n>=N){
						pair.add(new int[]{i,j});
						tmp.add(new Double(tDouble));
					}
				}
			}
		}
		return (List<int[]>)pair.subList(0, N>pair.size()?pair.size():N);
	}
	public ArrayList<Integer> getDistanceOrder(boolean symmetric)throws Exception{
		if(symmetric){
			return getDistanceOrder();
		}else{
			return null;
		}
	}
	/**
	 * for centroids
	 * @param dist
	 * @throws Exception
	 */
	public void calculateDistances()throws Exception{
		this.calculateDistances(true);
	}
	public void calculateDistances(boolean symmetric)throws Exception{
		distances=new DoubleMatrix(datum.size(),datum.size());
		distancesMap=new HashMap<Integer, Integer>();
		distancesMap_reverse=new HashMap<Integer, Integer>();
		double tmp=0;
		Object[] keyset=datum.keySet().toArray();
		for (int i = 0; i < keyset.length; i++) {
			distancesMap.put((Integer)keyset[i], new Integer(i));
			distancesMap_reverse.put(new Integer(i), (Integer)keyset[i]);
		}
		if(symmetric){
			for(int i=0;i<datum.size();i++){
				for(int j=0;j<=i;j++){
					tmp=((isodata_data)datum.get((Integer)keyset[i])).distanceTo( ((isodata_data)datum.get((Integer)keyset[j])) );
					distances.set(i,j,tmp);
					distances.set(j,i,tmp);
				}
			}
		}else{
			for(int i=0;i<datum.size();i++){
				for(int j=0;j<datum.size();j++){
					tmp=((isodata_data)datum.get((Integer)keyset[i])).distanceTo(((isodata_data)datum.get((Integer)keyset[j])));
					distances.set(i,j,tmp);
				}
			}
		}
	}
	/**
	 * for datum
	 * @return
	 */
	public ArrayList<Integer> getClusterOrder(){
		return getClusterOrder(allIndices);
	}
	private ArrayList<Integer> getClusterOrder(ArrayList<Integer> a){
		if(a.size()<=1){
			return a;
		}else{
			int pivot=((isodata_data)datum.get(((Integer)a.get(a.size()/2)))).getCluster().intValue();
			ArrayList<Integer> pivotList=new ArrayList<Integer>();
			ArrayList<Integer> less=new ArrayList<Integer>();
			ArrayList<Integer> more=new ArrayList<Integer>();
			for (Iterator iter = a.iterator(); iter.hasNext();) {
				Integer i = (Integer) iter.next();
				if(((isodata_data)datum.get(i)).getCluster().intValue()==pivot){
					pivotList.add(i);
				}else if(((isodata_data)datum.get(i)).getCluster().intValue()>pivot){
					more.add(i);
				}else{
					less.add(i);
				}
			}
			pivotList.addAll(0,this.getClusterOrder(less));
			pivotList.addAll(this.getClusterOrder(more));
			return pivotList;
		}
	}
}
